package core.reports;

import static org.junit.Assert.*;

import java.io.IOException;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Random;

import org.junit.Before;
import org.junit.Test;

import common.TimeSource;
import common.util.DateUtils;

import core.Container;
import core.ContainerFacade;
import core.Item;
import core.ItemFacade;
import core.Product;
import core.ProductFacade;
import core.ProductManager;
import core.exception.IllegalAddException;
import core.exception.ModelException;
import core.exception.SaveException;

import persistence.DataPersistor;

// Responsible for parts of test plan:
// 5d, 9d, 10 l-o, 12, 13 k-n, 14
// 
// 5d:  Test running the report on a day between 1-28 of a month
// 9d:  Test for 100 month period (should run)
// 10L. Test for a product that has usage every day during the period
// 10M. Test for a product that had no usage during the period 
//		(but usage the day before and still in storage)
// 10N. Test for a product that had no usage during the period 
//		(but used the last item the day before)
// 10O. Test for a product that had no usage during the period 
//		(but usage long before the period) and no items still in storage)
// 12.  Test an empty report
// 13K. 3-Month supply with 0 qty
// 13L. 3-Month supply with qty other than 0
// 13M. Shelf life with qty of 0
// 13N. Shelf life with qty greater than 1
// 14A. Items used in ascending/descending/random age order
// 14B. Test changing the entry date of an item (calculations should be based on new entry date)
// 14C. Test current supply
// 14D. Test avg supply
// 14E. Test min supply
// 14F. Test max supply
public class NatashaProductStatsTest {
	private float _avgAge;
	private int _maxAge;

	@Before
	public void setUp() throws Exception 
	{
		DataPersistor.instance().init("-serial");
		ContainerFacade.clearAllData();
		ItemFacade.clearAllData();
		ProductFacade.clearAllData();
		
		_avgAge = 0;
		_maxAge = 0;
		
	}
	

	// This test covers 5d, 10L-O, 13K-N, and 14a
	// It is run March 3, 2013 and covers 1 month (since February 3, 2013)
	@Test
	public void test1() throws ModelException, ParseException, IOException 
	{
		test1Setup();
		setTimeSource("03/03/2013");
		
		// Build report
		TestBuilder toTest = new TestBuilder("dne.txt","Product Report (1 Months)");
		ProductStatsVisitor visitor = new ProductStatsVisitor(toTest, 1);
		ProductManager.instance().accept(visitor);
		toTest.print();
		
		List<List<String>> results = toTest.getData();
		// First product alphabetically is ProductLastUsedDayBefore
		int curRow = 1;
		assertEquals("ProductLastUsedDayBefore", results.get(curRow).get(0));
		assertEquals("3456", results.get(curRow).get(1));
		assertEquals("5 grams", results.get(curRow).get(2));
		assertEquals("", results.get(curRow).get(3));
		assertEquals("0 / 0", results.get(curRow).get(4));
		assertEquals("0 / 0", results.get(curRow).get(5));
		assertEquals("0 / 0", results.get(curRow).get(6));
		assertEquals("", results.get(curRow).get(7));
		assertEquals("0 days /\n0 days", results.get(curRow).get(8));
		assertEquals("0 days /\n0 days", results.get(curRow).get(9));
		
		// Next product is ProductUsedDayBeforeAndStored
		curRow ++;
		assertEquals("ProductUsedDayBeforeAndStored", results.get(curRow).get(0));
		assertEquals("2345", results.get(curRow).get(1));
		assertEquals("4 gallons", results.get(curRow).get(2));
		assertEquals("1", results.get(curRow).get(3));
		assertEquals("4 / 4", results.get(curRow).get(4));
		assertEquals("4 / 4", results.get(curRow).get(5));
		assertEquals("0 / 0", results.get(curRow).get(6));
		assertEquals("1 months", results.get(curRow).get(7));
		assertEquals("0 days /\n0 days", results.get(curRow).get(8));
		assertEquals("30 days /\n30 days", results.get(curRow).get(9));
		
		// Next product is productUsedEachDay
		curRow ++;
		assertEquals("ProductUsedEachDay", results.get(curRow).get(0));
		assertEquals("1234", results.get(curRow).get(1));
		assertEquals("3 fluid ounces", results.get(curRow).get(2));
		assertEquals("", results.get(curRow).get(3));
		assertEquals("0 / 13", results.get(curRow).get(4)); 
		assertEquals("0 / 27", results.get(curRow).get(5));
		assertEquals("28 / 0", results.get(curRow).get(6));
		assertEquals("", results.get(curRow).get(7));
		DecimalFormat df = new DecimalFormat("#0.#");
		assertEquals(df.format(_avgAge) + " days /\n" + Integer.toString(_maxAge) + " days", 
				results.get(curRow).get(8));
		assertEquals("0 days /\n0 days", results.get(curRow).get(9));
		
		// Last product is ProductUsedLastItem3MonthAgo
		curRow ++;
		assertEquals("ProductUsedLastItem3MonthAgo",results.get(curRow).get(0));
		assertEquals("4567",results.get(curRow).get(1));
		assertEquals("6 pounds",results.get(curRow).get(2));
		assertEquals("",results.get(curRow).get(3));
		assertEquals("0 / 0",results.get(curRow).get(4));
		assertEquals("0 / 0",results.get(curRow).get(5));
		assertEquals("0 / 0",results.get(curRow).get(6));
		assertEquals("",results.get(curRow).get(7));
		assertEquals("0 days /\n0 days",results.get(curRow).get(8));
		assertEquals("0 days /\n0 days",results.get(curRow).get(9));
		
	}
	
	private void test1Setup() throws ModelException, ParseException
	{
		// The container
		Container theContainer = ContainerFacade.instance().addStorageUnit("THE");
		
		// For the items of productUsedLastItem3MonthsAgo
		Product productUsedLastItem3MonthsAgo = new Product("4567","ProductUsedLastItem3MonthAgo",
				"6", "pounds",0,0);	
		ProductFacade.instance().addProduct(productUsedLastItem3MonthsAgo);
			
		List<Item> i1 = ItemFacade.instance().createItems(productUsedLastItem3MonthsAgo, 
				theContainer,1,	"12/01/2012");
		
		setTimeSource("12/03/2012");
		ItemFacade.instance().removeItem(i1.get(0));
		
		// For the product used last day before report
		setTimeSource("02/01/2013");
		
		Product productLastUsedDayBefore = new Product("3456","ProductLastUsedDayBefore", "5",
				"grams",0,0);		
		ProductFacade.instance().addProduct(productLastUsedDayBefore);
		
		List<Item> i2 = ItemFacade.instance().createItems(productLastUsedDayBefore, theContainer,1,
				"02/01/2013");
		
		assertEquals(DateUtils.parseDate("02/01/2013"),i2.get(0).getEntryDate().getTime());

		setTimeSource("02/02/2013");
		assertEquals(DateUtils.parseDate("02/01/2013"),i2.get(0).getEntryDate().getTime());
		
		ItemFacade.instance().removeItem(i2.get(0));	
		assertEquals(DateUtils.parseDate("02/01/2013"),i2.get(0).getEntryDate().getTime());
		
		
		assertEquals(DateUtils.parseDate("02/02/2013"),i2.get(0).getExitDate().getTime());
		
		// For the product used last day before report but still has a supply
		Product productUsedDayBeforeAndStored = new Product("2345","ProductUsedDayBeforeAndStored",
				"4","gallons",1,1);		
		ProductFacade.instance().addProduct(productUsedDayBeforeAndStored);

		List<Item> i3 = ItemFacade.instance().createItems(productUsedDayBeforeAndStored, 
				theContainer,5,	"02/01/2013");
		
		setTimeSource("02/02/2013");
		ItemFacade.instance().removeItem(i3.get(0));	
		
		// Product Used each day
		Product productUsedEachDay = new Product("1234", "ProductUsedEachDay", "3", "fluid ounces", 
				0, 0);		
		ProductFacade.instance().addProduct(productUsedEachDay);
		
		List<Item> i4 = ItemFacade.instance().createItems(productUsedEachDay, theContainer, 15, 
				"01/31/2013");
		List<Item> i5 = ItemFacade.instance().createItems(productUsedEachDay, theContainer, 13, 
				"02/01/2013");
		
		// Effectively randomize order used
		i4.addAll(i5);
		Random generator = new Random();
		for (int i=0; i < i4.size(); i++)
		{
			// Get random indices
			int index1 = generator.nextInt(i4.size());
			int index2 = generator.nextInt(i4.size());
			
			// Swap
			Item temp = i4.get(index1);
			i4.set(index1, i4.get(index2));
			i4.set(index2, temp);
		}
		
		setTimeSource("02/03/2013");
		_avgAge = 0;
		for (int i=0; i < i4.size(); i++)
		{
			ItemFacade.instance().removeItem(i4.get(i));
			_avgAge += i4.get(i).age();
			if (i4.get(i).age() > _maxAge)
				_maxAge = i4.get(i).age();
			
			if (i == 25)
			{
				setTimeSource("03/01/2013");
			}
			else if (i == 26)
			{
				setTimeSource("03/02/2013");
			}
			else if (i == 27)
			{
				setTimeSource("03/03/2013");
			}
			else if (i < 6)
			{
				setTimeSource("02/0" + Integer.toString(i+4) + "/2013");
			}
			else
			{
				setTimeSource("02/" + Integer.toString(i+4) + "/2013");
			}
		}
		assertEquals(DateUtils.parseDate("02/03/2013"), i4.get(0).getExitDate().getTime());
		_avgAge /= (float)i4.size();

	}
	
	// This test 9d and 14a ascending
	// This test needs to cover 8 years and 4 months
	// It is run on March 1, 2013, and has an item added on the
	// first day of each month starting November 1, 2004 and and that same
	// item is removed on the 15th of the same month
	@Test
	public void test2() throws ModelException, ParseException, IOException
	{
		test2Setup();
		
		setTimeSource("03/01/2013");
		
		// Build report
		TestBuilder toTest = new TestBuilder("dne.txt","Product Report (100 Months)");
		ProductStatsVisitor visitor = new ProductStatsVisitor(toTest, 100);
		ProductManager.instance().accept(visitor);
		toTest.print();
		
		List<List<String>> results = toTest.getData();
		// First product alphabetically is ProductLastUsedDayBefore
		int curRow = 1;
		assertEquals("My 100 month used product", results.get(curRow).get(0));
		assertEquals("1234", results.get(curRow).get(1));
		assertEquals("4 kilograms", results.get(curRow).get(2));
		assertEquals("", results.get(curRow).get(3));
		assertEquals("1 / 0.5", results.get(curRow).get(4));
		assertEquals("0 / 1", results.get(curRow).get(5));
		assertEquals("100 / 101", results.get(curRow).get(6));
		assertEquals("", results.get(curRow).get(7));
		assertEquals("14 days /\n14 days", results.get(curRow).get(8));
		assertEquals("0 days /\n0 days", results.get(curRow).get(9));		
	}
	
	private void test2Setup() throws ModelException, ParseException
	{
		Product myProd = new Product("1234","My 100 month used product","4","kilograms",0,0);
		ProductFacade.instance().addProduct(myProd);
		Container theContainer = ContainerFacade.instance().addStorageUnit("mySU");
		
		Calendar c = Calendar.getInstance();
		c.setTime(DateUtils.parseDate("11/01/2004"));
		for (int i=0; i < 100; i++)
		{
			Calendar myCal = (Calendar) c.clone();
			myCal.add(Calendar.MONTH, i);
			
			List<Item> items = ItemFacade.instance().createItems(myProd, theContainer, 1, 
					DateUtils.formatDate(myCal.getTime()));
			
			Calendar removeCal = (Calendar) myCal.clone();
			removeCal.add(Calendar.DATE, 14);
			setTimeSource(DateUtils.formatDate(removeCal.getTime()));
			ItemFacade.instance().removeItem(items.get(0));
			
		}
		
		ItemFacade.instance().createItems(myProd, theContainer, 1, "03/01/2013");
		
	}
	
	
	private void setTimeSource(String dateString) throws ParseException
	{
		Calendar c= Calendar.getInstance();
		c.setTime(DateUtils.parseDate(dateString));
		TimeSource.instance().setTime(c);	
	}

	// This test just covers test 12, which is an empty report. So, the idea is just
	// run the report and make sure nothing dies.
	@Test
	public void test3() throws IOException
	{
		// Build report
		TestBuilder toTest = new TestBuilder("dne.txt","Product Report (100 Months)");
		ProductStatsVisitor visitor = new ProductStatsVisitor(toTest, 1);
		ProductManager.instance().accept(visitor);
		toTest.print();	
		
		List<List<String>> results = toTest.getData();
		assertEquals("Test builder has 4 empty rows, but this really is working",5,results.size());
	}
	
	// This test covers 14a descending and 14b
	// It is run on February 1, 2013 and covers one month.
	@Test
	public void test4() throws ModelException, ParseException, IOException 
	{
		test4Setup();
		setTimeSource("02/01/2013");
		
		// Build report
		TestBuilder toTest = new TestBuilder("dne.txt","Product Report (1 Months)");
		ProductStatsVisitor visitor = new ProductStatsVisitor(toTest, 1);
		ProductManager.instance().accept(visitor);
		toTest.print();
		
		List<List<String>> results = toTest.getData();	
		int curRow = 1;
		
		assertEquals("My Test 4 Product", results.get(curRow).get(0));
		assertEquals("12234", results.get(curRow).get(1));
		assertEquals("4 kilograms", results.get(curRow).get(2));
		assertEquals("", results.get(curRow).get(3));
		assertEquals("0 / 1.7", results.get(curRow).get(4)); 
		assertEquals("0 / 5", results.get(curRow).get(5));
		assertEquals("5 / 5", results.get(curRow).get(6));
		assertEquals("", results.get(curRow).get(7));
		DecimalFormat df = new DecimalFormat("#0.#");
		assertEquals(df.format(_avgAge) + " days /\n" + Integer.toString(_maxAge) + " days", 
				results.get(curRow).get(8));
		assertEquals("0 days /\n0 days", results.get(curRow).get(9));		
	}
	
	private void test4Setup() throws ModelException, ParseException
	{
		Product myProd = new Product("12234","My Test 4 Product","4","kilograms",0,0);
		ProductFacade.instance().addProduct(myProd);
		Container theContainer = ContainerFacade.instance().addStorageUnit("mySU");		
		
		List<Item> descending = new ArrayList<Item>();
		descending.addAll(ItemFacade.instance().createItems(myProd, theContainer, 1, "01/15/2013"));
		descending.addAll(ItemFacade.instance().createItems(myProd, theContainer, 1, "01/16/2013"));
		descending.addAll(ItemFacade.instance().createItems(myProd, theContainer, 1, "01/17/2013"));		
		descending.addAll(ItemFacade.instance().createItems(myProd, theContainer, 1, "01/18/2013"));
		descending.addAll(ItemFacade.instance().createItems(myProd, theContainer, 1, "01/20/2013"));
		
		descending.get(4).changeEntryDate("01/19/2013");
		
		_avgAge = 0;
		_maxAge = 0;
		Calendar c = Calendar.getInstance();
		c.setTime(DateUtils.parseDate("01/21/2013"));
		int addDays = 0;
		
		for(int i= descending.size()-1; i >=0; i--)
		{
			Calendar myCal = (Calendar) c.clone();
			myCal.add(Calendar.DATE, addDays++);
			setTimeSource(DateUtils.formatDate(myCal.getTime()));

			ItemFacade.instance().removeItem(descending.get(i));
			_avgAge += descending.get(i).age();
			
			if (_maxAge < descending.get(i).age())
			{
				_maxAge = descending.get(i).age();
			}
		}
		_avgAge /= descending.size();
		
	}
	
	// This test is sort of for 14C-F, but mostly it's because I'm not
	// at 500 lines yet. It is run on 2/01/2013 for 1 month
	@Test
	public void test5() throws ParseException, IOException, ModelException
	{
		test5Setup();
		setTimeSource("02/01/2013");
		
		// Build report
		TestBuilder toTest = new TestBuilder("dne.txt","Product Report (1 Months)");
		ProductStatsVisitor visitor = new ProductStatsVisitor(toTest, 1);
		ProductManager.instance().accept(visitor);
		toTest.print();
		
		List<List<String>> results = toTest.getData();	
		int curRow = 1;
		

		assertEquals("Product created before time period, but no added " +
				"items till the last day", results.get(curRow).get(0));
		assertEquals("666", results.get(curRow).get(1));
		assertEquals("4 kilograms", results.get(curRow).get(2));
		assertEquals("", results.get(curRow).get(3));
		assertEquals("32 / 1", results.get(curRow).get(4)); 
		assertEquals("0 / 32", results.get(curRow).get(5));
		assertEquals("0 / 32", results.get(curRow).get(6));
		assertEquals("", results.get(curRow).get(7));
		assertEquals("0 days /\n0 days", 
				results.get(curRow).get(8));
		assertEquals("0 days /\n0 days", results.get(curRow).get(9));		
		
		curRow ++;
		assertEquals("Product made on last day of time", 
				results.get(curRow).get(0));
		assertEquals("777", results.get(curRow).get(1));
		assertEquals("4 kilograms", results.get(curRow).get(2));
		assertEquals("", results.get(curRow).get(3));
		assertEquals("1 / 1", results.get(curRow).get(4)); 
		assertEquals("1 / 1", results.get(curRow).get(5));
		assertEquals("0 / 1", results.get(curRow).get(6));
		assertEquals("", results.get(curRow).get(7));
		assertEquals("0 days /\n0 days", 
				results.get(curRow).get(8));
		assertEquals("0 days /\n0 days", results.get(curRow).get(9));		
		
		curRow ++;
		assertEquals("Product with initial supply of 5 and then increases", 
				results.get(curRow).get(0));
		assertEquals("1567", results.get(curRow).get(1));
		assertEquals("4 kilograms", results.get(curRow).get(2));
		assertEquals("", results.get(curRow).get(3));
		assertEquals("36 / 21.5", results.get(curRow).get(4)); 
		assertEquals("6 / 36", results.get(curRow).get(5));
		assertEquals("0 / 31", results.get(curRow).get(6));
		assertEquals("", results.get(curRow).get(7));
		assertEquals("0 days /\n0 days", 
				results.get(curRow).get(8));
		assertEquals("18.2 days /\n32 days", results.get(curRow).get(9));			
		
	
	}
	
	private void test5Setup() throws ModelException, ParseException
	{
		Product myProd = new Product("1567","Product with initial supply of 5 and then increases",
				"4","kilograms",0,0);
		ProductFacade.instance().addProduct(myProd);
		Container theContainer = ContainerFacade.instance().addStorageUnit("mySU");		
		
		ItemFacade.instance().createItems(myProd, theContainer, 5, "12/31/2012");
		
		Calendar c = Calendar.getInstance();
		c.setTime(DateUtils.parseDate("01/01/2013"));
		int addDays = 0;
		
		for(int i= 0; i < 31; i++)
		{
			Calendar myCal = (Calendar) c.clone();
			myCal.add(Calendar.DATE, addDays++);
			ItemFacade.instance().createItems(myProd, theContainer, 1, 
					DateUtils.formatDate(myCal.getTime()));
		}
		
		Product myProd2 = new Product("666","Product created before time period, but no added " +
				"items till the last day",
				"4","kilograms",0,0);
		ProductFacade.instance().addProduct(myProd2);		
		
		List<Item> items = ItemFacade.instance().createItems(myProd2, theContainer, 1, "12/31/2012");
		setTimeSource("12/31/2012");
		ItemFacade.instance().removeItem(items.get(0));
		
		setTimeSource("02/01/2013");
		
		ItemFacade.instance().createItems(myProd2, theContainer, 32, "02/01/2013");
		
		Product myProd3 = new Product("777","Product made on last day of time",
				"4","kilograms",0,0);
		ProductFacade.instance().addProduct(myProd3);		
		
		ItemFacade.instance().createItems(myProd3, theContainer, 1, "02/01/2013");
		
	}
}
